#include<linux/module.h>
#include<linux/printk.h>
#include<linux/kobject.h>
#include<linux/kernel.h>
#include<linux/string.h>
#include<linux/kallsyms.h>
#include<linux/sched.h>
#include<linux/cpu.h>
#include<linux/pid_namespace.h>
#include<linux/mmzone.h>
#include<linux/nodemask.h>
#include<linux/mm.h>
#include<linux/page-flags.h>
#include<net/net_namespace.h>
#include<net/rtnetlink.h>
#include<linux/netdevice.h>
#include<linux/inetdevice.h>
#include<linux/slab.h>
#include<linux/slub_def.h>
#include<linux/types.h>
#include <linux/namei.h>
#include <linux/version.h>

#include "common.h"

int prepend(char **buffer, int *buflen, const char *str, int namelen)
{
    *buflen -= namelen;
    if (*buflen < 0)
        return -1;
    *buffer -= namelen;
    memcpy(*buffer, str, namelen);
    return 0;
}

static struct mount *inode2mount(struct inode *inode)
{
    struct list_head *pos;
    struct mount *mount = NULL;

    if(inode && !_IS_ERR(inode)){
        pos = inode->i_sb->s_mounts.next;
        if(pos && !_IS_ERR(pos)){
            mount = container_of(pos, struct mount, mnt_instance);
        }
    }
    return mount;
}


static int mnt_has_parent(struct mount *mnt)
{
    return !!(mnt != mnt->mnt_parent);
}

static struct dentry *__lock_parent(struct dentry *dentry)
{
    struct dentry *parent;
    rcu_read_lock();
    spin_unlock(&dentry->d_lock);
again:
    parent = READ_ONCE(dentry->d_parent);
    spin_lock(&parent->d_lock);
    if (unlikely(parent != dentry->d_parent)) {
        spin_unlock(&parent->d_lock);
        goto again;
    }
    rcu_read_unlock();
    if (parent != dentry)
        spin_lock_nested(&dentry->d_lock, DENTRY_D_LOCK_NESTED);
    else
        parent = NULL;
    return parent;
}

static inline struct dentry *lock_parent(struct dentry *dentry)
{
    struct dentry *parent = dentry->d_parent;
    if (IS_ROOT(dentry))
        return NULL;
    if (likely(spin_trylock(&parent->d_lock)))
        return parent;
    return __lock_parent(dentry);
}

static inline void __dget_dlock(struct dentry *dentry)
{
    dentry->d_lockref.count++;
}

static struct dentry *__d_find_alias(struct inode *inode)
{
    struct dentry *alias;

    if (S_ISDIR(inode->i_mode))
        return NULL;

#ifndef LINUX_310
    hlist_for_each_entry(alias, &inode->i_dentry, d_u.d_alias)
#else
    hlist_for_each_entry(alias, &inode->i_dentry, d_alias)
#endif
    {
        spin_lock(&alias->d_lock);
        __dget_dlock(alias);
        spin_unlock(&alias->d_lock);
        return alias;
    }
    return NULL;
}

static struct dentry *_d_find_alias(struct inode *inode)
{
    struct dentry *de = NULL;

    if (!hlist_empty(&inode->i_dentry)) {
        spin_lock(&inode->i_lock);
        de = __d_find_alias(inode);
        spin_unlock(&inode->i_lock);
    }
    return de;
}



static char *dentry_name(struct inode *inode, struct dentry *dentry, char *name, int len)
{
    struct mount *mnt;
    struct mount *prev = NULL;
    char *p;
    char *tmp;
    char *end;
    int ret = 0;

    tmp = kmalloc(PATH_MAX, GFP_ATOMIC);
    if (!tmp)
        return NULL;

    end = name;
    end[len - 1]  = 0;
    end = name + len - 1;

    mnt = inode2mount(inode);
    do {
        memset(tmp, 0, PATH_MAX);
        p = dentry_path_raw(dentry, tmp, PATH_MAX);
        //pr_err("%s-%d:inode %px mountpoint %px  dentry:%px\n", p, strlen(p), inode, mnt->mnt_mountpoint, dentry);
        ret = prepend(&end, &len, p, strlen(p));
        if (ret)
            pr_err("prepend error\n");
        //pr_err("mnt:%px parent:%px end:%px len %d tmp:%px\n", mnt,mnt->mnt_parent, end, len, tmp);
        prev = mnt;
        dentry = mnt->mnt_mountpoint;
        mnt = mnt->mnt_parent;
    } while (mnt_has_parent(prev) && (dentry != mnt->mnt_mountpoint));

    kfree(tmp);
    memmove(name, end, strlen(end) + 1);
    return name;
}

int scan_inode_name(struct inode *inode, char *buf, int len, unsigned long *cached, int *deleted)
{
    struct dentry *dt;
    struct dentry *parent;

    *cached = inode->i_data.nrpages;
    dt = _d_find_alias(inode);
    if (!dt) {
        *deleted = 1;
        return 0;
    }

    spin_lock(&inode->i_lock);
    spin_lock(&dt->d_lock);
    parent = lock_parent(dt);

    dentry_name(inode, dt, buf, len);
    *deleted = d_unlinked(dt);

    if (parent)
        spin_unlock(&parent->d_lock);
    spin_unlock(&dt->d_lock);
    spin_unlock(&inode->i_lock);
    dput(dt);

    return 0;
}

void radix_init(struct radix_tree_root *root)
{
    INIT_RADIX_TREE(root, GFP_ATOMIC);
}

int radix_insert(struct radix_tree_root *root, unsigned long key, void *ptr)
{
    return radix_tree_insert(root, key, ptr);
}

void *radix_lookup(struct radix_tree_root *root, unsigned long key)
{
    return (void *)radix_tree_lookup(root, key);
}

int radix_delete(struct radix_tree_root *root, unsigned long key)
{
    return radix_tree_delete(root, key);
}
#undef NR
#define NR (10)
int radix_delete_all(struct radix_tree_root *root, node_free_t free)
{
    int found, i;
    unsigned long pos = 0;
    struct radix_item *res[NR];

    do {
        found = radix_tree_gang_lookup(root, (void **)res, pos, NR);
        for (i = 0; i < found; i++) {
            radix_delete(root, res[i]->key);
            if (free)
                free(res[i]);
        }
    } while (found > 0);

    return 0;
}
